OPC Studio User's Guide and Reference
Processing generic data
View with Navigation Tools
Concepts > QuickOPC Concepts > QuickOPC Extensions > QuickOPC Integrated Extensions > OPC UA Complex Data Extension > Generic data and data types > Working with generic data > Processing generic data

In order to process generic data (a sub-type of GenericData Class), you need to first determine its kind (see Generic data kinds). You can do it by

All generic data classes share a common DataType Property, which contains an optional data type object.

After you have determined the kind of generic data you are dealing with, you can use the generic data object cast to appropriate type, and work with properties and methods that are specific to that data type kind. For example, SequenceData contains the elements of the sequence in its Elements Property.

The following example shows how generic data can be processed.

.NET

// Shows how to process generic data type, displaying some of its properties, recursively.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
// OPC client and subscriber examples in C# on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-CSharp .
// Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
// a commercial license in order to use Online Forums, and we reply to every post.

using System;
using System.Collections.Generic;
using OpcLabs.BaseLib.DataTypeModel;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.ComplexData;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples.ComplexData._GenericData
{
    class DataTypeKind1
    {
        public static void Main1()
        {
            // Define which server and node we will work with.
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
            // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            // or "https://opcua.demo-this.com:51212/UA/SampleServer/"
            UANodeDescriptor nodeDescriptor =
                "nsu=http://test.org/UA/Data/ ;i=10239"; // [ObjectsFolder]/Data.Static.Scalar.StructureValue

            // Instantiate the client object.
            var client = new EasyUAClient();

            // Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
            UAGenericObject genericObject;
            try
            {
                genericObject = (UAGenericObject)client.ReadValue(endpointDescriptor, nodeDescriptor);
            }
            catch (UAException uaException)
            {
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException().Message);
                return;
            }

            // Process the generic data type. We will inspect some of its properties, and dump them.
            ProcessGenericData(genericObject.GenericData, maximumDepth: 3);
        }
        

        // Process the generic data type. Its structure can sometimes be quite deep, therefore we are limiting the depth
        // of the recursion using maximumDepth.
        public static void ProcessGenericData(GenericData genericData, int maximumDepth)
        {
            if (maximumDepth == 0)
                return;

            Console.WriteLine();
            Console.WriteLine("genericData.DataType: {0}", genericData.DataType);

            switch (genericData.DataTypeKind)
            {
                case DataTypeKind.Enumeration:
                    Console.WriteLine("The generic data is an enumeration.");
                    var enumerationData = (EnumerationData) genericData;
                    Console.WriteLine("Its value is {0}.", enumerationData.Value);
                    // There is also a ValueName that you can inspect (if known).
                    break;

                case DataTypeKind.Opaque:
                    Console.WriteLine("The generic data is opaque.");
                    var opaqueData = (OpaqueData) genericData;
                    Console.WriteLine("Its size is {0} bits.", opaqueData.SizeInBits);
                    Console.WriteLine("The data bytes are {0}.", BitConverter.ToString(opaqueData.ByteArray));
                    // Use the Value property (a BitArray) if you need to access the value bit by bit.
                    break;

                case DataTypeKind.Primitive:
                    Console.WriteLine("The generic data is primitive.");
                    var primitiveData = (PrimitiveData) genericData;
                    Console.WriteLine("Its value is \"{0}\".", primitiveData.Value);
                    break;

                case DataTypeKind.Sequence:
                    Console.WriteLine("The generic data is a sequence.");
                    var sequenceData = (SequenceData) genericData;
                    Console.WriteLine("It has {0} elements.", sequenceData.Elements.Count);
                    Console.WriteLine("A dump of the elements follows.");
                    foreach (GenericData element in sequenceData.Elements)
                        ProcessGenericData(element, maximumDepth - 1);
                    break;

                case DataTypeKind.Structured:
                    Console.WriteLine("The generic data is structured.");
                    var structuredData = (StructuredData) genericData;
                    Console.WriteLine("It has {0} field data members.", structuredData.FieldData.Count);
                    Console.WriteLine("The names of the fields are: {0}.",
                        String.Join(", ", structuredData.FieldData.Keys));

                    Console.WriteLine("A dump of each of the fields follows.");
                    foreach (KeyValuePair<string, GenericData> pair in structuredData.FieldData)
                    {
                        Console.WriteLine();
                        Console.WriteLine("Field name: {0}", pair.Key);
                        ProcessGenericData(pair.Value, maximumDepth - 1);
                    }
                    break;

                case DataTypeKind.Union:
                    Console.WriteLine("The generic data is a union.");
                    var unionData = (UnionData)genericData;
                    Console.WriteLine("The name of current field is: {0}", unionData.FieldName);
                    Console.WriteLine("Current field value is: {0}", unionData.FieldValue);
                    break;
            }
        }
    }
}

COM

// Shows how to process generic data type, displaying some of its properties, recursively
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
// OPC client and subscriber examples in Object Pascal (Delphi) on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-OP .
// Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
// a commercial license in order to use Online Forums, and we reply to every post.

class procedure DataTypeKind1.Main;
var
  Client: _EasyUAClient;
  EndpointDescriptor: string;
  GenericObject: _UAGenericObject;
  NodeDescriptor: string;
begin
  EndpointDescriptor := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  NodeDescriptor := 'nsu=http://test.org/UA/Data/ ;i=10239';  // [ObjectsFolder]/Data.Static.Scalar.StructureValue

  // Instantiate the client object
  Client := CoEasyUAClient.Create;

  // Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.

  try
    GenericObject := _UAGenericObject(IUnknown(Client.ReadValue(EndpointDescriptor, NodeDescriptor)));
  except
    on E: EOleException do
    begin
      WriteLn(Format('*** Failure: %s', [E.GetBaseException.Message]));
      Exit;
    end;
  end;

  // Process the generic data type. We will inspect some of its properties, and dump them.
  ProcessGenericData(GenericObject.GenericData, 2);
end;

Function VariantToBytes(Const Value: OleVariant): TBytes;
Var
  Size: Integer;
  pData: Pointer;
Begin
  Size := Succ(VarArrayHighBound(Value, 1) - VarArrayLowBound(Value, 1));
  SetLength(Result, Size);
  pData := VarArrayLock(Value);
  Try
    Move(pData^, Pointer(Result)^, Size);
  Finally
    VarArrayUnlock(Value);
  End;
End;

class procedure DatatypeKind1.ProcessGenericData(GenericData: OpcLabs_BaseLib_TLB._GenericData; MaximumDepth: Cardinal);
var
  ByteArray: OleVariant;
  Count: Cardinal;
  Element: OleVariant;
  ElementEnumerator: IEnumVARIANT;
  EnumerationData: _EnumerationData;
  First: boolean;
  Keys: string;
  OpaqueData: _OpaqueData;
  PrimitiveData: _PrimitiveData;
  SequenceData: _SequenceData;
  StructuredData: _StructuredData;
  Value: OpcLabs_BaseLib_TLB._GenericData;
begin
  if MaximumDepth = 0 then
    Exit;

  WriteLn;
  WriteLn('genericData.DataType: ', GenericData.DataType.ToString);

  case GenericData.DataTypeKind of
    DataTypeKind_Enumeration:
      begin
        WriteLn('The generic data is an enumeration.');
        EnumerationData := GenericData as _EnumerationData;
        WriteLn(Format('Its value is %s.', [EnumerationData.Value.ToString]));
        // There is also a ValueName that you can inspect (if known).
      end;
    DataTypeKind_Opaque:
      begin
        WriteLn('The generic data is opaque.');
        OpaqueData := GenericData as _OpaqueData;
        WriteLn(Format('Its size is %d bits.', [OpaqueData.SizeInBits]));
        TVarData(ByteArray).VType := varArray;
        TVarData(ByteArray).VArray := PVarArray(OpaqueData.ByteArray);
        WriteLn(Format('The data bytes are %s.', [TEncoding.ANSI.GetString(VariantToBytes(ByteArray))]));
        // Use the Value property (a BitArray) if you need to access the value bit by bit.
      end;
    DataTypeKind_Primitive:
      begin
        WriteLn('The generic data is primitive.');
        PrimitiveData := GenericData as _PrimitiveData;
        WriteLn(Format('Its value is "%s".', [PrimitiveData.Value]));
      end;
    DataTypeKind_Sequence:
      begin
        WriteLn('The generic data is a sequence.');
        SequenceData := GenericData as _SequenceData;
        WriteLn(Format('It has %s elements.', [SequenceData.Elements.Count.ToString]));
        WriteLn('A dump of the elements follows.');
        ElementEnumerator := SequenceData.Elements.GetEnumerator;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          ProcessGenericData(IUnknown(Element) as OpcLabs_BaseLib_TLB._GenericData, MaximumDepth - 1);
        end;
      end;
    DataTypeKind_Structured:
      begin
        WriteLn('The generic data is structured.');
        StructuredData := GenericData as _StructuredData;
        WriteLn(Format('It has %s field data members.', [StructuredData.FieldData.Count.ToString]));
        ElementEnumerator := StructuredData.FieldData.GetEnumerator;
        Keys := '';
        First := True;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          if First then
            First := False
          else
            Keys := Keys + ', ';
          Keys := Keys + Element.Key;
        end;
        WriteLn(Format('The names of the fields are: %s.', [Keys]));

        WriteLn('A dump of each of the fields follows.');
        ElementEnumerator := StructuredData.FieldData.GetEnumerator;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          WriteLn;
          WriteLn(Format('Field name: %s', [Element.Key]));
          Value := IUnknown(Element.Value) as OpcLabs_BaseLib_TLB._GenericData;
          ProcessGenericData(Value, MaximumDepth - 1);
        end;
      end;
  end;

end;

Python

# Shows how to process generic data type, displaying some of its properties, recursively.
#
# Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
# OPC client and subscriber examples in Python on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-Python .
# Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
# a commercial license in order to use Online Forums, and we reply to every post.
# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc

# Import .NET namespaces.
from System import *
from OpcLabs.BaseLib.DataTypeModel import *
from OpcLabs.EasyOpc.UA import *
from OpcLabs.EasyOpc.UA.OperationModel import *


def processGenericData(genericData, maximumDepth):
    if maximumDepth == 0:
        print('* Reached maximum depth *')
        return

    print()
    print('genericData.DataType: ', genericData.DataType, sep='')

    dataTypeKind = genericData.DataTypeKind
    if dataTypeKind == DataTypeKind.Enumeration:
        print('The generic data is an enumeration.')
        enumerationData = genericData
        print('Its value is ', enumerationData.Value, '.', sep='')
        # There is also a ValueName that you can inspect (if known).

    elif dataTypeKind == DataTypeKind.Opaque:
        print('The generic data is opaque.')
        opaqueData = genericData
        print('Its size is ', opaqueData.SizeInBits, ' bits.', sep='')
        print('The data bytes are ', BitConverter.ToString(opaqueData.ByteArray), '.', sep='')
        # Use the Value property (a BitArray) if you need to access the value bit by bit.

    elif dataTypeKind == DataTypeKind.Primitive:
        print('The generic data is primitive.')
        primitiveData = genericData
        print('Its value is "', primitiveData.Value, '".', sep='')

    elif dataTypeKind == DataTypeKind.Sequence:
        print('The generic data is a sequence.')
        sequenceData = genericData
        print('It has ', sequenceData.Elements.Count, ' elements.', sep='')
        print('A dump of the elements follows.')
        for element in sequenceData.Elements:
            processGenericData(element, maximumDepth - 1)

    elif dataTypeKind == DataTypeKind.Structured:
        print('The generic data is structured.')
        structuredData = genericData
        print('It has ', structuredData.FieldData.Count, ' field data members.', sep='')
        print('The names of the fields are: ', end='')
        for i, name in enumerate(structuredData.FieldData.Keys):
            if i != 0:
                print(', ', end='')
            print(name, end='')
        print('.')

        print('A dump of each of the fields follows.')
        for pair in structuredData.FieldData:
            print()
            print('Field name: ', pair.Key, sep='')
            processGenericData(pair.Value, maximumDepth - 1)

    elif dataTypeKind == DataTypeKind.Union:
        print('The generic data is a union.')
        unionData = genericData
        print('The name of current field is: ', unionData.FieldName, sep='')
        print('Current field value is: ', unionData.FieldValue, end='')


# Define which server and node we will work with.
endpointDescriptor = UAEndpointDescriptor('opc.tcp://opcua.demo-this.com:51210/UA/SampleServer')
# or 'http://opcua.demo-this.com:51211/UA/SampleServer' (currently not supported)
# or 'https://opcua.demo-this.com:51212/UA/SampleServer/'

# [ObjectsFolder]/Data.Static.Scalar.StructureValue
nodeDescriptor = UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10239')

# Instantiate the client object.
client = EasyUAClient()

# Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
try:
    print('Reading...')
    genericObject = IEasyUAClientExtension.ReadValue(client, endpointDescriptor, nodeDescriptor)
except UAException as uaException:
    print('*** Failure: ' + uaException.GetBaseException().Message)
    exit()
print('Reading successful.')

# Process the generic data type. We will inspect some of its properties, and dump them.
processGenericData(genericObject.GenericData, 3)

print()
print('Finished.')

 

 

In many cases, you will not be processing the generic data alone, but in parallel with the data type. See Processing complex data types for details. 

See Also

Reference